//  This file is part of NHibernate.ReLinq.Sample a sample showing
//  the use of the open source re-linq library to implement a non-trivial 
//  Linq-provider, on the example of NHibernate (www.nhibernate.org).
//  Copyright (C) 2005-2009 rubicon informationstechnologie gmbh, www.rubicon.eu
//  
//  NHibernate.ReLinq.Sample is based on re-motion re-linq (http://www.re-motion.org/).
//  
//  NHibernate.ReLinq.Sample is free software; you can redistribute it 
//  and/or modify it under the terms of the MIT License 
// (http://www.opensource.org/licenses/mit-license.php).
// 
using System;
using System.Linq.Expressions;
using System.Text;
using Remotion.Linq.Clauses.Expressions;
using Remotion.Linq.Parsing;

namespace NHibernate.ReLinq.Sample.HqlQueryGeneration
{
    /// <summary>
    /// 继承 ThrowingExpressionVisitor
    /// 如果没有显式重写指定类型的表达式树，则会抛出错误
    /// 
    /// 可以避免解析遗漏的场景
    /// 
    /// </summary>
    public class HqlGeneratorExpressionTreeVisitor : ThrowingExpressionVisitor
    {
        public static string GetHqlExpression(Expression linqExpression, ParameterAggregator parameterAggregator)
        {
            // 传递需要保存解析参数的容器
            var visitor = new HqlGeneratorExpressionTreeVisitor(parameterAggregator);
            // 执行 Visitor
            visitor.Visit(linqExpression);
            // 扩展方法
            // 通过 Visitor 过程中保存的临时变量，将其转换成 Hql
            return visitor.GetHqlExpression();
        }

        private int _deep = 1;
        private char _flag = '-';

        // 这个 StringBuilder 用于 Where 语句的组建
        private readonly StringBuilder _hqlExpression = new StringBuilder();

        // 用于保存解析的参数
        private readonly ParameterAggregator _parameterAggregator;

        private HqlGeneratorExpressionTreeVisitor(ParameterAggregator parameterAggregator)
        {
            _parameterAggregator = parameterAggregator;
        }

        public string GetHqlExpression()
        {
            return _hqlExpression.ToString();
        }

        /// <summary>
        /// 解析 select pn 语句
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        protected override Expression VisitQuerySourceReference(QuerySourceReferenceExpression expression)
        {
            // 例如，解析 select pn 中的 pn
            var tp = expression.ReferencedQuerySource.ItemName;
            _hqlExpression.Append(tp);
            Console.WriteLine($"{Flag}VisitQuerySourceReferenceExpression -> {tp}");

            return expression;
        }

        /// <summary>
        /// 解析二元操作
        /// 例如 pn.Name=="KKKing"
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        protected override Expression VisitBinary(BinaryExpression expression)
        {
            _hqlExpression.Append("(");

            Console.WriteLine($"{Flag}VisitBinaryExpression -> (");

            _deep++;
            Visit(expression.Left);
            _deep--;
            // In production code, handle this via lookup tables.
            switch (expression.NodeType)
            {
                case ExpressionType.Equal:
                    Console.WriteLine($"{Flag}VisitBinaryExpression-NodeType -> =");
                    _hqlExpression.Append(" = ");
                    break;

                case ExpressionType.AndAlso:
                case ExpressionType.And:
                    Console.WriteLine($"{Flag}VisitBinaryExpression-NodeType -> and");
                    _hqlExpression.Append(" and ");
                    break;

                case ExpressionType.OrElse:
                case ExpressionType.Or:
                    Console.WriteLine($"{Flag}VisitBinaryExpression-NodeType -> or");
                    _hqlExpression.Append(" or ");
                    break;

                case ExpressionType.Add:
                    Console.WriteLine($"{Flag}VisitBinaryExpression-NodeType -> +");
                    _hqlExpression.Append(" + ");
                    break;

                case ExpressionType.Subtract:
                    Console.WriteLine($"{Flag}VisitBinaryExpression-NodeType -> -");
                    _hqlExpression.Append(" - ");
                    break;

                case ExpressionType.Multiply:
                    Console.WriteLine($"{Flag}VisitBinaryExpression-NodeType -> *");
                    _hqlExpression.Append(" * ");
                    break;

                case ExpressionType.Divide:
                    Console.WriteLine($"{Flag}VisitBinaryExpression-NodeType -> /");
                    _hqlExpression.Append(" / ");
                    break;

                default:
                    base.VisitBinary(expression);
                    break;
            }
            _deep++;
            Visit(expression.Right);
            _deep--;

            Console.WriteLine($"{Flag}VisitBinaryExpression -> )");
            _hqlExpression.Append(")");

            return expression;
        }

        /// <summary>
        /// 解析属性访问
        /// 例如 pn.Name=="KKKing" 中的 pn.Name
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        protected override Expression VisitMember(MemberExpression expression)
        {
            // 如果是 pn.CountryCode
            // 将会访问 VisitQuerySourceReferenceExpression
            // _hqlExpression 中将会添加 pn
            _deep++;
            Visit(expression.Expression);
            _deep++;
            // 最后格式化为 pn.CountryCode
            _hqlExpression.AppendFormat(".{0}", expression.Member.Name);
            Console.WriteLine($"{Flag}VisitMemberExpression -> .{expression.Member.Name}");

            return expression;
        }

        /// <summary>
        /// 解析常量
        /// 例如 pn.Name=="KKKing" 中的 "KKKing"
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        protected override Expression VisitConstant(ConstantExpression expression)
        {
            var namedParameter = _parameterAggregator.AddParameter(expression.Value);
            _hqlExpression.AppendFormat(":{0}", namedParameter.Name);
            Console.WriteLine($"{Flag}VisitConstantExpression -> :{namedParameter.Name}");

            return expression;
        }

        /// <summary>
        /// 解析方法调用
        /// 例如 pn.Name.Contains("KKKing")
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        protected override Expression VisitMethodCall(MethodCallExpression expression)
        {
            // In production code, handle this via method lookup tables.

            var supportedMethod = typeof(string).GetMethod("Contains");
            if (expression.Method.Equals(supportedMethod))
            {
                _hqlExpression.Append("(");
                _deep++;
                Visit(expression.Object);
                _deep++;
                _hqlExpression.Append(" like '%'+");
                _deep++;
                Visit(expression.Arguments[0]);
                _deep++;
                _hqlExpression.Append("+'%')");
                return expression;
            }
            else
            {
                return base.VisitMethodCall(expression); // throws
            }
        }

        // 如果无法解析表达式树，则抛出这个异常
        // Called when a LINQ expression type is not handled above.
        protected override Exception CreateUnhandledItemException<T>(T unhandledItem, string visitMethod)
        {
            string itemText = unhandledItem?.ToString();
            var message = string.Format("The expression '{0}' (type: {1}) is not supported by this LINQ provider.", itemText, typeof(T));
            return new NotSupportedException(message);
        }

        private String Flag
        {
            get
            {
                return new string(_flag, _deep * 3);
            }
        }
    }
}